package hips.images.integer;

import hips.Image;
import hips.Partition;
import hips.Region;
import hips.tools.ListPixels;
import hips.tools.Neighborhood;
import hips.tools.WorkingData;
import hips.tools.Neighborhood.Iterator;
import hips.region.NewRegionEvent;

public class PartitionCompleteInt<PixelVal extends PixelValueInt<PixelVal>> extends Partition<PixelVal>{

	private int lblval;
	private int[] rl;
	private QueueInt PQueue;
	private ListPixels refs;
	private ListPixels stack;
	private ListPixels all;
	private ListPixels reserve;
	private int edges;
	private int edgesBelowAlpha;
	private Neighborhood neighbors;
	private boolean[] connected;
	private PixelVal fp;
	private PixelVal neighbor;
	private PixelVal pv;
	private Image<PixelVal> f;
	private int alpha;
	
	public PartitionCompleteInt(Image<PixelVal> input, PixelVal alpha, PixelVal omega, float ci, int randomPixels) {
		super(input, alpha, omega, ci, randomPixels);
		method = Partition.IMPROVED_WITH_CINDEX;
	}
	
	public PartitionCompleteInt(Image<PixelVal> input) {
		super(input);
		method = Partition.IMPROVED_WITH_CINDEX;
	}

	public void makeRegions() {
		WorkingData<PixelVal> w = getWorkingData();
        alpha = w.getAlpha().intValue();
        f = w.getImage();
        PixelVal omega = w.getOmega();
		neighbors = new Neighborhood(f.getWidth(), f.getHeight());
		PQueue = new QueueInt(256);
		pixels = new int[f.getSize()];
		refs = new ListPixels(f.getSize());
		reserve = new ListPixels(f.getSize());
		all = new ListPixels(f.getSize());
		stack = new ListPixels(f.getSize());
		int base = 0;
		lbl = new int[f.getSize()];
		rl = new int[f.getSize()];
		connected = new boolean[f.getSize()];
		lblval = 0;
		for (int i = 0; i < f.getSize(); i++) {
			lbl[i] = -1;
			rl[i] = Integer.MAX_VALUE;
			connected[i] = false;
		}
		PixelVal mincc = f.newPixelValue();
		PixelVal maxcc = f.newPixelValue();
		fp = f.newPixelValue();
		neighbor = f.newPixelValue();
		pv = f.newPixelValue();
		for (int p = -randomPixels; p < f.getSize(); p++) {
			int pix = p;
			if (p < 0) {
				pix = (int) (Math.random() * f.getSize());
			}
			if (lbl[pix] == -1) {
				stack.clear();
				reserve.clear();
				all.clear();
				f.getPixelValue(mincc, pix);
				f.getPixelValue(maxcc, pix);
				int localMax = alpha + 1;
				int localCurrent = 0;
				edges = 0;
				edgesBelowAlpha = 0;
				PQueue.add(pix, 0);
				while (!PQueue.isEmpty()) {
					PixelPriorityInt datum = PQueue.remove(localCurrent);
					if (rl[datum.getPixel()] == -1) {
						continue;
					}
					if (datum.getPriority() > localCurrent) {
						addStackPixels();
						localCurrent = datum.getPriority();
					}
					stack.add(datum.getPixel());
					rl[datum.getPixel()] = -1;
					f.getPixelValue(fp, datum.getPixel());
					mincc.setLower(fp);
					maxcc.setGreater(fp);
					if (!omega.isGreaterOrEqual(pv.range(maxcc, mincc)) || localCurrent >= localMax) {
						discardQueueAndStack();
						break;
					}
					for (Iterator q = neighbors.iterator(datum.getPixel()); q.hasNext();) {
						int range = pv.range(fp, f.getPixelValue(neighbor, q.get())).maxValueInt();
						if ((lbl[q.get()] != -1) && (lbl[q.get()] != lblval) && localMax > range) {
							localMax = range;
							if (localCurrent >= localMax) {
								discardQueueAndStack();
								break;
							}
						} else if (range < localMax && rl[q.get()] != -1 && range < rl[q.get()]) {
							rl[q.get()] = range;
							PQueue.add(q.get(), range);
						}
					}
				}
				addStackPixels();
				stack.clearIterator();
				while (stack.hasNew()) {
					int r = stack.getNew();
					rl[r] = Integer.MAX_VALUE;
					connected[r] = false;
				}
				for (int i = 0; i < all.getSize(); i++) {
					pixels[base + i] = all.getPixel(i);
				}
				refs.add(base);
				Region region = new Region(pixels, base, all.getSize(), lblval);
				fireRegionEvent(new NewRegionEvent(this, region));
				base += all.getSize();
				lblval++;
			}
		}
		references = new int[refs.getSize()];
		for (int i = 0; i < refs.getSize(); i++) {
			references[i] = refs.getPixel(i);
		}
		releaseData();
	}
	
	private void addStackPixels() {
		while (stack.hasNew()) {
			int r = stack.getNew();
			f.getPixelValue(fp, r);
			for (Iterator q = neighbors.iterator(r); q.hasNext();) {
				if (connected[q.get()]) {
					edges++;
					if (pv.range(fp, f.getPixelValue(neighbor, q.get())).maxValueInt() <= alpha) {
						edgesBelowAlpha++;
					}
				}
			}
			connected[r] = true;
			reserve.add(r);
		}
		if (edges == 0 || ((float) edgesBelowAlpha / (float) edges) >= getCindex()) {
			for (int i = 0; i < reserve.getSize(); i++) {
				lbl[reserve.getPixel(i)] = lblval;
				all.add(reserve.getPixel(i));
			}
			reserve.clear();
		}
	}

	private void discardQueueAndStack() {
		while (!PQueue.isEmpty()) {
			rl[PQueue.remove().getPixel()] = Integer.MAX_VALUE;
		}
		while (stack.hasNew()) {
			rl[stack.getNew()] = Integer.MAX_VALUE;
		}
	}
	
	private void releaseData() {
		rl = null;
		PQueue = null;
		refs = null;
		stack = null;
		all = null;
		reserve = null;
		neighbors = null;
		connected = null;
		fp = null;
		neighbor = null;
		pv = null;
		f = null;
	}
}
